From 0e8f8d50aaee1a2f0364a253b19c62f5d20567b2 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 15 Aug 2014 17:57:16 -0700 Subject: [PATCH] Add an exclude key to the manifest This key is used to help determine the set of input files for a package. The normal method (via walking or git ls-files) is filtered via all the patterns provided in `exclude` of the project section. The toml key is a string array corresponding to a set of glob patterns according to the syntax of libglob (provided in the standard distribution). The set of files normally found will be filtered by each pattern, and if any pattern matches then the path is excluded. This will later on be used for `cargo package` when generating a tarball to get uploaded. --- src/cargo/core/manifest.rs | 8 ++- src/cargo/lib.rs | 1 + src/cargo/sources/path.rs | 99 ++++++++++++++++++++----------------- src/cargo/util/toml.rs | 7 ++- tests/test_cargo_compile.rs | 43 +++++++++++++++- 5 files changed, 108 insertions(+), 50 deletions(-) diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index 2d7eedd70..6d7fe2dd2 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -24,6 +24,7 @@ pub struct Manifest { sources: Vec, build: Vec, warnings: Vec, + exclude: Vec, } impl Show for Manifest { @@ -312,7 +313,7 @@ impl Show for Target { impl Manifest { pub fn new(summary: &Summary, targets: &[Target], target_dir: &Path, doc_dir: &Path, sources: Vec, - build: Vec) -> Manifest { + build: Vec, exclude: Vec) -> Manifest { Manifest { summary: summary.clone(), authors: Vec::new(), @@ -322,6 +323,7 @@ impl Manifest { sources: sources, build: build, warnings: Vec::new(), + exclude: exclude, } } @@ -376,6 +378,10 @@ impl Manifest { pub fn get_warnings(&self) -> &[String] { self.warnings.as_slice() } + + pub fn get_exclude(&self) -> &[String] { + self.exclude.as_slice() + } } impl Target { diff --git a/src/cargo/lib.rs b/src/cargo/lib.rs index f701553d3..ba092de4b 100644 --- a/src/cargo/lib.rs +++ b/src/cargo/lib.rs @@ -7,6 +7,7 @@ extern crate collections; extern crate debug; +extern crate glob; extern crate regex; extern crate semver; extern crate serialize; diff --git a/src/cargo/sources/path.rs b/src/cargo/sources/path.rs index c7d0e7d51..0d65a19c7 100644 --- a/src/cargo/sources/path.rs +++ b/src/cargo/sources/path.rs @@ -2,6 +2,7 @@ use std::cmp; use std::fmt::{Show, Formatter}; use std::fmt; use std::io::fs; +use glob::Pattern; use core::{Package, PackageId, Summary, SourceId, Source, Dependency, Registry}; use ops; @@ -68,61 +69,69 @@ impl PathSource { /// are relevant for building this package, but it also contains logic to /// use other methods like .gitignore to filter the list of files. pub fn list_files(&self, pkg: &Package) -> CargoResult> { - // TODO: add an `excludes` section to the manifest which is another way - // to filter files out of this set that is returned. - return if self.path.join(".git").exists() { + let candidates = try!(if self.path.join(".git").exists() { self.list_files_git(pkg) } else { self.list_files_walk(pkg) - }; - - fn list_files_git(&self, pkg: &Package) -> CargoResult> { - let cwd = pkg.get_manifest_path().dir_path(); - let mut cmd = process("git").cwd(cwd.clone()); - cmd = cmd.arg("ls-files").arg("-z"); - - // Filter out all other packages with a filter directive - for pkg in self.packages.iter().filter(|p| *p != pkg) { - if cwd.is_ancestor_of(pkg.get_manifest_path()) { - let filter = pkg.get_manifest_path().dir_path() - .path_relative_from(&self.path).unwrap(); - cmd = cmd.arg("-x").arg(filter); - } + }); + + let pats = pkg.get_manifest().get_exclude().iter().map(|p| { + Pattern::new(p.as_slice()) + }).collect::>(); + + let root = pkg.get_manifest_path().dir_path(); + Ok(candidates.move_iter().filter(|candidate| { + let candidate = candidate.path_relative_from(&root).unwrap(); + !pats.iter().any(|p| p.matches_path(&candidate)) + }).collect()) + } + + fn list_files_git(&self, pkg: &Package) -> CargoResult> { + let cwd = pkg.get_manifest_path().dir_path(); + let mut cmd = process("git").cwd(cwd.clone()); + cmd = cmd.arg("ls-files").arg("-z"); + + // Filter out all other packages with a filter directive + for pkg in self.packages.iter().filter(|p| *p != pkg) { + if cwd.is_ancestor_of(pkg.get_manifest_path()) { + let filter = pkg.get_manifest_path().dir_path() + .path_relative_from(&self.path).unwrap(); + cmd = cmd.arg("-x").arg(filter); } + } - log!(5, "listing git files with: {}", cmd); - let output = try!(cmd.arg(".").exec_with_output()); - let output = output.output.as_slice(); - Ok(output.split(|x| *x == 0).map(Path::new).collect()) + log!(5, "listing git files with: {}", cmd); + let output = try!(cmd.arg(".").exec_with_output()); + let output = output.output.as_slice(); + Ok(output.split(|x| *x == 0).map(|p| cwd.join(p)).collect()) + } + + fn list_files_walk(&self, pkg: &Package) -> CargoResult> { + let mut ret = Vec::new(); + for pkg in self.packages.iter().filter(|p| *p == pkg) { + let loc = pkg.get_manifest_path().dir_path(); + try!(walk(&loc, &mut ret, true)); } + return Ok(ret); - fn list_files_walk(&self, pkg: &Package) -> CargoResult> { - let mut ret = Vec::new(); - for pkg in self.packages.iter().filter(|p| *p == pkg) { - let loc = pkg.get_manifest_path().dir_path(); - try!(walk(&loc, &mut ret, true)); + fn walk(path: &Path, ret: &mut Vec, + is_root: bool) -> CargoResult<()> { + if !path.is_dir() { + ret.push(path.clone()); + return Ok(()) } - return Ok(ret); - - fn walk(path: &Path, ret: &mut Vec, - is_root: bool) -> CargoResult<()> { - if !path.is_dir() { - ret.push(path.clone()); - return Ok(()) - } - // Don't recurse into any sub-packages that we have - if !is_root && path.join("Cargo.toml").exists() { return Ok(()) } - for dir in try!(fs::readdir(path)).iter() { - match (is_root, dir.filename_str()) { - (_, Some(".git")) | - (true, Some("target")) | - (true, Some("Cargo.lock")) => continue, - _ => {} - } - try!(walk(dir, ret, false)); + // Don't recurse into any sub-packages that we have + if !is_root && path.join("Cargo.toml").exists() { return Ok(()) } + for dir in try!(fs::readdir(path)).iter() { + match (is_root, dir.filename_str()) { + (_, Some(".git")) | + (true, Some("target")) | + (true, Some("Cargo.lock")) => continue, + _ => {} } - return Ok(()) + try!(walk(dir, ret, false)); } + return Ok(()) } } } diff --git a/src/cargo/util/toml.rs b/src/cargo/util/toml.rs index a5bf74c49..03445dccd 100644 --- a/src/cargo/util/toml.rs +++ b/src/cargo/util/toml.rs @@ -195,7 +195,7 @@ pub struct TomlManifest { test: Option>, bench: Option>, dependencies: Option>, - dev_dependencies: Option> + dev_dependencies: Option>, } #[deriving(Encodable,Decodable,PartialEq,Clone)] @@ -220,6 +220,7 @@ pub struct TomlProject { pub version: String, pub authors: Vec, build: Option, + exclude: Option>, } #[deriving(Encodable,Decodable,PartialEq,Clone,Show)] @@ -418,6 +419,7 @@ impl TomlManifest { Some(MultipleBuildCommands(ref cmd)) => cmd.clone(), None => Vec::new() }; + let exclude = project.exclude.clone().unwrap_or(Vec::new()); let summary = Summary::new(&pkgid, deps.as_slice()); let mut manifest = Manifest::new(&summary, @@ -425,7 +427,8 @@ impl TomlManifest { &layout.root.join("target"), &layout.root.join("doc"), sources, - build); + build, + exclude); if used_deprecated_lib { manifest.add_warning(format!("the [[lib]] section has been \ deprecated in favor of [lib]")); diff --git a/tests/test_cargo_compile.rs b/tests/test_cargo_compile.rs index 4931a6cf9..c5834741f 100644 --- a/tests/test_cargo_compile.rs +++ b/tests/test_cargo_compile.rs @@ -1,10 +1,11 @@ -use std::io::{fs, TempDir}; +use std::io::{fs, TempDir, File}; use std::os; use std::path; use support::{ResultTest, project, execs, main_file, basic_bin_manifest}; -use support::{COMPILING, RUNNING, cargo_dir, ProjectBuilder, path2url}; +use support::{COMPILING, RUNNING, FRESH, cargo_dir, ProjectBuilder, path2url}; use hamcrest::{assert_that, existing_file}; +use support::paths::PathExt; use cargo; use cargo::util::{process, realpath}; @@ -1486,3 +1487,41 @@ test!(deprecated_lib { .with_stderr("\ the [[lib]] section has been deprecated in favor of [lib]\n")); }) + +test!(freshness_ignores_excluded { + let foo = project("foo") + .file("Cargo.toml", r#" + [package] + name = "foo" + version = "0.0.0" + authors = [] + build = "true" + exclude = ["src/b*.rs"] + "#) + .file("src/lib.rs", "pub fn bar() -> int { 1 }"); + foo.build(); + foo.root().move_into_the_past().assert(); + + assert_that(foo.process(cargo_dir().join("cargo-build")), + execs().with_status(0) + .with_stdout(format!("\ +{compiling} foo v0.0.0 ({url}) +", compiling = COMPILING, url = foo.url()))); + + // Smoke test to make sure it doesn't compile again + println!("first pass"); + assert_that(foo.process(cargo_dir().join("cargo-build")), + execs().with_status(0) + .with_stdout(format!("\ +{fresh} foo v0.0.0 ({url}) +", fresh = FRESH, url = foo.url()))); + + // Modify an ignored file and make sure we don't rebuild + println!("second pass"); + File::create(&foo.root().join("src/bar.rs")).assert(); + assert_that(foo.process(cargo_dir().join("cargo-build")), + execs().with_status(0) + .with_stdout(format!("\ +{fresh} foo v0.0.0 ({url}) +", fresh = FRESH, url = foo.url()))); +}) -- 2.30.2